/**
 * Copyright (c) 2011-2023, James Zhan 詹波 (jfinal@126.com).
 *
 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.demo.common;

import cn.hutool.v7.core.convert.ConvertUtil;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import org.noear.snack4.ONode;

/**
 * Model. 不能使用复合主键？？
 *
 * <p>A clever person solves a problem. A wise person avoids it. A stupid person makes it.
 *
 * @author airhead
 */
@SuppressWarnings({
  "rawtypes",
  "unchecked",
  "AlibabaAbstractClassShouldStartWithAbstractNaming",
  "AlibabaAvoidStartWithDollarAndUnderLineNaming"
})
public abstract class Model<M extends Model> implements IRow<M>, Serializable {
  public static final int FILTER_BY_SAVE = 0;
  public static final int FILTER_BY_UPDATE = 1;
  String configName;

  /** Flag of column has been modified. update need this flag */
  Set<String> modifyFlag;

  /** Attributes of this model */
  private Map<String, Object> attrs = createAttrsMap();

  public M use(String configName) {
    this.configName = configName;
    return (M) this;
  }

  public M dao() {
    //    attrs = DaoContainerFactory.daoMap;
    //    modifyFlag = DaoContainerFactory.daoSet;
    return (M) this;
  }

  public Map<String, Object> _getAttrs() {
    return attrs;
  }

  public Set<Map.Entry<String, Object>> _getAttrsEntrySet() {
    return attrs.entrySet();
  }

  /** Return attribute names of this model. */
  public String[] _getAttrNames() {
    Set<String> attrNameSet = attrs.keySet();
    return attrNameSet.toArray(new String[attrNameSet.size()]);
  }

  /** Return attribute values of this model. */
  public Object[] _getAttrValues() {
    Collection<Object> attrValueCollection = attrs.values();
    return attrValueCollection.toArray(new Object[attrValueCollection.size()]);
  }

  /**
   * Set attributes with other model.
   *
   * @param model the Model
   * @return this Model
   */
  public <T extends Model> M _setAttrs(T model) {
    return (M) _setAttrs(model._getAttrs());
  }

  @Override
  public Map<String, Object> toMap() {
    return attrs;
  }

  @Override
  public M put(Map<String, Object> map) {
    return null;
  }

  @Override
  public M put(String key, Object value) {
    attrs.put(key, value);
    return (M) this;
  }

  @Override
  public M set(String column, Object value) {
    attrs.put(column, value);
    return (M) this;
  }

  @Override
  public <T> T get(String column) {
    if (attrs.get(column) == null) {
      return null;
    }

    return (T) attrs.get(column);
  }

  @Override
  public <T> T get(String column, T defaultValue) {
    if (attrs.get(column) == null) {
      return defaultValue;
    }

    return (T) attrs.get(column);
  }

  @Override
  public <T> T get(String column, T defaultValue, Func.F11<Object, T> converter) {
    if (attrs.get(column) == null) {
      return defaultValue;
    }

    return converter.call(attrs.get(column));
  }

  @Override
  public String getStr(String column) {
    return ConvertUtil.toStr(attrs.get(column));
  }

  @Override
  public Integer getInt(String column) {
    return ConvertUtil.toInt(attrs.get(column));
  }

  @Override
  public Long getLong(String column) {
    return ConvertUtil.toLong(attrs.get(column));
  }

  @Override
  public BigInteger getBigInteger(String column) {
    return ConvertUtil.toBigInteger(attrs.get(column));
  }

  @Override
  public Date getDate(String column) {
    return ConvertUtil.toDate(attrs.get(column));
  }

  @Override
  public LocalDateTime getLocalDateTime(String column) {
    return ConvertUtil.toLocalDateTime(attrs.get(column));
  }

  @Override
  public Time getTime(String column) {
    return null;
  }

  @Override
  public Timestamp getTimestamp(String column) {
    return null;
  }

  @Override
  public Double getDouble(String column) {
    return ConvertUtil.toDouble(attrs.get(column));
  }

  @Override
  public Float getFloat(String column) {
    return ConvertUtil.toFloat(attrs.get(column));
  }

  @Override
  public Short getShort(String column) {
    return ConvertUtil.toShort(attrs.get(column));
  }

  @Override
  public Byte getByte(String column) {
    return ConvertUtil.toByte(attrs.get(column));
  }

  @Override
  public Boolean getBoolean(String column) {
    return ConvertUtil.toBoolean(attrs.get(column));
  }

  @Override
  public BigDecimal getBigDecimal(String column) {
    return ConvertUtil.toBigDecimal(attrs.get(column));
  }

  @Override
  public byte[] getBytes(String column) {
    return ConvertUtil.toPrimitiveByteArray(attrs.get(column));
  }

  @Override
  public Number getNumber(String column) {
    return ConvertUtil.toNumber(attrs.get(column));
  }

  @Override
  public String toJson() {
    return ONode.serialize(attrs);
  }

  @Override
  public int size() {
    return attrs.size();
  }

  public M remove(String attr) {
    attrs.remove(attr);
    return (M) this;
  }

  public M remove(String... attrs) {
    if (attrs != null) {
      for (String a : attrs) {
        this.attrs.remove(a);
      }
    }
    return (M) this;
  }

  public M removeNullValueAttrs() {
    for (Iterator<Map.Entry<String, Object>> it = attrs.entrySet().iterator(); it.hasNext(); ) {
      Map.Entry<String, Object> e = it.next();
      if (e.getValue() == null) {
        it.remove();
      }
    }
    return (M) this;
  }

  public M keep(String... attrs) {
    if (attrs != null && attrs.length > 0) {
      Map<String, Object> newAttrs = createAttrsMap();
      for (String attr : attrs) {
        if (this.attrs.containsKey(attr)) {
          newAttrs.put(attr, this.attrs.get(attr));
        }
      }

      this.attrs = newAttrs;
    } else {
      this.attrs.clear();
    }

    return (M) this;
  }

  public M keep(String attr) {
    // prevent put null value to the newColumns
    if (attrs.containsKey(attr)) {
      Object keepIt = attrs.get(attr);
      attrs.clear();
      clearModifyFlag();
      attrs.put(attr, keepIt);
    } else {
      attrs.clear();
      clearModifyFlag();
    }

    return (M) this;
  }

  public M clear() {
    attrs.clear();
    clearModifyFlag();
    return (M) this;
  }

  /**
   * Set attributes with Map.
   *
   * @param attrs attributes of this model
   * @return this Model
   */
  protected M _setAttrs(Map<String, Object> attrs) {
    if (attrs != null) {
      for (Map.Entry<String, Object> e : attrs.entrySet()) {
        set(e.getKey(), e.getValue());
      }
    }
    return (M) this;
  }

  private Map<String, Object> createAttrsMap() {
    Map<String, Object> attrs = new HashMap<>(0);

    return attrs;
  }

  void clearModifyFlag() {
    if (modifyFlag != null) {
      modifyFlag.clear();
    }
  }
}
